#pragma once


// Copyright (c) 2010 Manuel Peinado Gallego <manuel.peinado@gmail.com>
// Distributed under the MIT license

// Mesh.h : interface of the Mesh class
//

#include "Geometry.h"
#include "Material.h"
#include "Transformable.h"
#include "MeshRenderPolicy.h"
#include "AABB.h"
#include <string>
#include <vector>
class ShaderProgram;
class TransformNode;
class MeshManager;
class MeshBufferInstance;
class Mesh;

typedef std::vector<gx::Vec3f> TexCoordList;

class MeshBuffer : public Geometry, public VertexBufferMeshRenderPolicy<MeshBuffer>
{
public:
    MeshBuffer();
    ~MeshBuffer();

    // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

    ref<MeshBufferInstance> makeInstance(Mesh* owner);

    // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

    int getId() const { return m_id; }

    // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

    void setVertices(const std::vector<gx::Vec3f>& v);
    void setNormals(const std::vector<gx::Vec3f>& n);
    void setTexCoords(const std::vector<gx::Vec3f>& tc, size_t unit=0);

    const std::vector<GLuint>& getIndices() const { return m_indices; }
    const std::vector<gx::Vec3f>& getVertices() const { return m_vertices; }
    const std::vector<gx::Vec3f>& getNormals() const { return m_normals; }
    size_t getNumTexCoordLayers() const { return m_texCoords.size(); }
    const std::vector<gx::Vec3f>& getTexCoordLayer(int i) const { return m_texCoords[i]; }

    bool hasNormals() const { return !m_normals.empty(); }
    bool isIndexed() const { return !m_indices.empty(); }

    // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 
    // Geometry overrides
    
    void draw(View3d* view) const override;
    AABB getAABB() const override;
    bool supportsTexturing() const override;
    ref<Geometry> clone() const override { throw std::exception(); }

    // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

    MeshBuffer& makeSphere(float R=1.f, int space=10);
    MeshBuffer& makeIndexedSphere(float R=1.f, int space=10);
    MeshBuffer& makeCube();
    MeshBuffer& makeTriangle();
    MeshBuffer& makeIndexedTriangle();
    MeshBuffer& makeIndexedCylinder(float R=0.5f, float length=1.f, 
                                    int nbSides=4);

protected:
    
    void addFace(const gx::Vec3f& v0, const gx::Vec3f& v1, 
                 const gx::Vec3f& v2, const gx::Vec3f& t0, 
                 const gx::Vec3f& t1, const gx::Vec3f& t2,
                 const gx::Vec3f& n);
    void addFace(const gx::Vec3f& v0, const gx::Vec3f& v1, 
                 const gx::Vec3f& v2, const gx::Vec3f& t0, 
                 const gx::Vec3f& t1, const gx::Vec3f& t2,
                 const gx::Vec3f& n0, const gx::Vec3f& n1,
                 const gx::Vec3f& n2);
    void addIndices(GLuint a, GLuint b, GLuint c);

    // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

    void clear();

private:

    friend class MeshManager;
    void setId(int id) { m_id = id; }
    void setManager(MeshManager* manager) { m_manager = manager; }
    MeshManager* getManager() const { return m_manager; }
    int m_id;
    MeshManager* m_manager;

    // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 


    // Vertices (3*i, 3*i+1, 3*i+2) form a face
    std::vector<gx::Vec3f> m_vertices;

    // Either m_vertices.size() == m_normals.size() or m_normals.size()==0
    std::vector<gx::Vec3f> m_normals;

    // Empty is mesh is not indexed
    std::vector<GLuint> m_indices;

    // Texture coordinates. There can be an arbitrary number of layers. During
    // rendering, if the material has more layers than texture coordinate layers
    // we have here, the first layer tex coords will be used in the missing ones
    
    std::vector<TexCoordList> m_texCoords;
};

class Mesh;
class Mesh;

class MeshBufferInstance : public Selectable, public Transformable
{
public:

    MeshBufferInstance(Mesh* owner, ref<Geometry> geometry);

    // --------------------------------------------------------------

    void addTextureTransform(size_t texLayer, const gx::Mat4& transform,
                             ref<TransformNode> owner);
    // NULL if not set
    TextureTransform* getTextureTransform(size_t texLayer);
    // Layer must be set
    void removeTextureTransform(size_t texLayer);

    // --------------------------------------------------------------

    bool isWireframe() const;
    void setWireframe(bool newVal=true);

    // --------------------------------------------------------------

    AABB getAABB() const;

    // --------------------------------------------------------------

    bool hasSide(GeometrySide side) const;
    Geometry* getGeometry() const;
    ShaderProgram* getShaderProgram() const;

    void setFrontMaterial(ref<Material> mat);
    void setBackMaterial(ref<Material> mat);

    void setShaderProgram(ref<ShaderProgram> prog);

    Material* getFrontMaterial() const;
    Material* getBackMaterial() const;
    
private:

    Mesh* m_owner;

    ref<Geometry> m_geometry;
    ref<Material> m_frontMaterial;
    ref<Material> m_backMaterial;
    ref<ShaderProgram> m_shaderProgram;

    bool m_wireframe; // false by default

    std::vector<ref<TextureTransform> > m_textureMatrices;
};


// ----------------------------------------------------------------------------
// Class "Mesh"
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------
// ----------------------------------------------------------------------------

class Mesh : public Geometry
{
public:

    Mesh();
    ~Mesh();

    // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

    ref<MeshBufferInstance> addMeshBuffer(ref<MeshBuffer> meshBuff);

    size_t getMeshBufferInstanceCount() const;
    MeshBufferInstance* getMeshBufferInstance(size_t index) const;

    // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 
    // Geometry overrides
    
    void draw(View3d* view) const override;
    AABB getAABB() const override;

    ref<Geometry> clone() const override { throw std::exception(); }

    // = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = 

private:

    // TODO make this a member of MBI
    void drawMeshBufferInstance(View3d* view, 
                                const MeshBufferInstance& mbi) const;
    void drawMbiSide(View3d* view, const MeshBufferInstance& mbi, 
                     GeometrySide side) const;

    typedef std::vector<ref<MeshBufferInstance> > MeshBufferInstanceList;
    MeshBufferInstanceList m_meshBufferInstances;
};
